/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/// The common infrastructure for ps_quad_* shaders.
///
/// # Memory layout
///
/// The diagram below shows the the various pieces of data fectched in the vertex shader:
///
///```ascii
///                                       (int gpu buffer)
///                                       +---------------+    (sGpuCache)
///  (instance-step vertex attr)          |  Int header   |   +-----------+
/// +-----------------------------+       |               |   | Transform |
/// |    Quad instance (uvec4)    |  +--> | transform id +--> +-----------+
/// |                             |  |    | z id          |
/// | x: int prim address        +---+    +---------------+   (float gpu buffer)
/// | y: float prim address      +--------------------------> +-----------+--------------+-+-+
/// | z: quad flags               |      (sGpuCache)          | Quad Prim | Quad Segment | | |
/// |    edge flags               |   +--------------------+  |           |              | | |
/// |    part index               |   |     Picture task   |  | bounds    | rect         | | |
/// |    segment index            |   |                    |  | clip      | uv rect      | | |
/// | w: picture task address    +--> | task rect          |  | color     |              | | |
/// +-----------------------------+   | device pixel scale |  +-----------+--------------+-+-+
///                                   | content origin     |
///                                   +--------------------+
///
/// To use the quad infrastructure, a shader must define the following entry
/// points in the corresponding shader stages:
/// - void pattern_vertex(PrimitiveInfo prim)
/// - vec4 pattern_fragment(vec4 base_color)
///```

#define WR_FEATURE_TEXTURE_2D

#include shared,rect,transform,render_task,gpu_buffer

flat varying mediump vec4 v_color;
// z: is_mask
// w: has edge flags
// x,y are avaible for patterns to use.
flat varying lowp ivec4 v_flags;
#define v_flags_is_mask v_flags.z
#define v_flags_has_edge_mask v_flags.w


#ifndef SWGL_ANTIALIAS
varying highp vec2 vLocalPos;
#endif

#ifdef WR_VERTEX_SHADER

#define EDGE_AA_LEFT    1
#define EDGE_AA_TOP     2
#define EDGE_AA_RIGHT   4
#define EDGE_AA_BOTTOM  8

#define PART_CENTER     0
#define PART_LEFT       1
#define PART_TOP        2
#define PART_RIGHT      3
#define PART_BOTTOM     4
#define PART_ALL        5

#define QF_IS_OPAQUE            1
#define QF_APPLY_DEVICE_CLIP    2
#define QF_IGNORE_DEVICE_SCALE  4
#define QF_USE_AA_SEGMENTS      8
#define QF_IS_MASK              16

#define INVALID_SEGMENT_INDEX   0xff

#define AA_PIXEL_RADIUS 2.0

PER_INSTANCE in ivec4 aData;

struct QuadSegment {
    RectWithEndpoint rect;
    RectWithEndpoint uv_rect;
};

struct PrimitiveInfo {
    vec2 local_pos;

    RectWithEndpoint local_prim_rect;
    RectWithEndpoint local_clip_rect;

    QuadSegment segment;

    int edge_flags;
    int quad_flags;
    ivec2 pattern_input;
};

struct QuadPrimitive {
    RectWithEndpoint bounds;
    RectWithEndpoint clip;
    RectWithEndpoint uv_rect;
    vec4 pattern_scale_offset;
    vec4 color;
};

QuadSegment fetch_segment(int base, int index) {
    QuadSegment seg;

    vec4 texels[2] = fetch_from_gpu_buffer_2f(base + 5 + index * 2);

    seg.rect = RectWithEndpoint(texels[0].xy, texels[0].zw);
    seg.uv_rect = RectWithEndpoint(texels[1].xy, texels[1].zw);

    return seg;
}

QuadPrimitive fetch_primitive(int index) {
    QuadPrimitive prim;

    vec4 texels[5] = fetch_from_gpu_buffer_5f(index);

    prim.bounds = RectWithEndpoint(texels[0].xy, texels[0].zw);
    prim.clip = RectWithEndpoint(texels[1].xy, texels[1].zw);
    prim.uv_rect = RectWithEndpoint(texels[2].xy, texels[2].zw);
    prim.pattern_scale_offset = texels[3];
    prim.color = texels[4];

    return prim;
}

struct QuadHeader {
    int transform_id;
    int z_id;
    ivec2 pattern_input;
};

QuadHeader fetch_header(int address) {
    ivec4 header = fetch_from_gpu_buffer_1i(address);

    QuadHeader qh = QuadHeader(
        header.x,
        header.y,
        header.zw
    );

    return qh;
}

struct QuadInstance {
    // x
    int prim_address_i;

    // y
    int prim_address_f;

    // z
    int quad_flags;
    int edge_flags;
    int part_index;
    int segment_index;

    // w
    int picture_task_address;
};

QuadInstance decode_instance() {
    QuadInstance qi = QuadInstance(
        aData.x,

        aData.y,

        (aData.z >> 24) & 0xff,
        (aData.z >> 16) & 0xff,
        (aData.z >>  8) & 0xff,
        (aData.z >>  0) & 0xff,

        aData.w
    );

    return qi;
}

struct VertexInfo {
    vec2 local_pos;
};

VertexInfo write_vertex(vec2 local_pos,
                        float z,
                        Transform transform,
                        vec2 content_origin,
                        RectWithEndpoint task_rect,
                        float device_pixel_scale,
                        int quad_flags) {
    VertexInfo vi;

    // Transform the current vertex to world space.
    vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);

    // Convert the world positions to device pixel space.
    vec2 device_pos = world_pos.xy * device_pixel_scale;

    if ((quad_flags & QF_APPLY_DEVICE_CLIP) != 0) {
        RectWithEndpoint device_clip_rect = RectWithEndpoint(
            content_origin,
            content_origin + task_rect.p1 - task_rect.p0
        );

        // Clip to task rect
        device_pos = rect_clamp(device_clip_rect, device_pos);

        vi.local_pos = (transform.inv_m * vec4(device_pos / device_pixel_scale, 0.0, 1.0)).xy;
    } else {
        vi.local_pos = local_pos;
    }

    // Apply offsets for the render task to get correct screen location.
    vec2 final_offset = -content_origin + task_rect.p0;

    gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);

    return vi;
}

float edge_aa_offset(int edge, int flags) {
    return ((flags & edge) != 0) ? AA_PIXEL_RADIUS : 0.0;
}

void pattern_vertex(PrimitiveInfo prim);

vec2 scale_offset_map_point(vec4 scale_offset, vec2 p) {
    return p * scale_offset.xy + scale_offset.zw;
}

RectWithEndpoint scale_offset_map_rect(vec4 scale_offset, RectWithEndpoint r) {
    return RectWithEndpoint(
        scale_offset_map_point(scale_offset, r.p0),
        scale_offset_map_point(scale_offset, r.p1)
    );
}

PrimitiveInfo quad_primive_info(void) {
    QuadInstance qi = decode_instance();

    QuadHeader qh = fetch_header(qi.prim_address_i);
    Transform transform = fetch_transform(qh.transform_id);
    PictureTask task = fetch_picture_task(qi.picture_task_address);
    QuadPrimitive prim = fetch_primitive(qi.prim_address_f);
    float z = float(qh.z_id);

    QuadSegment seg;
    if (qi.segment_index == INVALID_SEGMENT_INDEX) {
        seg.rect = prim.bounds;
        seg.uv_rect = prim.uv_rect;
    } else {
        seg = fetch_segment(qi.prim_address_f, qi.segment_index);
    }

    // The local space rect that we will draw, which is effectively:
    //  - The tile within the primitive we will draw
    //  - Intersected with any local-space clip rect(s)
    //  - Expanded for AA edges where appropriate
    RectWithEndpoint local_coverage_rect = seg.rect;

    // Apply local clip rect
    local_coverage_rect.p0 = max(local_coverage_rect.p0, prim.clip.p0);
    local_coverage_rect.p1 = min(local_coverage_rect.p1, prim.clip.p1);
    local_coverage_rect.p1 = max(local_coverage_rect.p0, local_coverage_rect.p1);

    switch (qi.part_index) {
        case PART_LEFT:
            local_coverage_rect.p1.x = local_coverage_rect.p0.x + AA_PIXEL_RADIUS;
#ifdef SWGL_ANTIALIAS
            swgl_antiAlias(EDGE_AA_LEFT);
#else
            local_coverage_rect.p0.x -= AA_PIXEL_RADIUS;
            local_coverage_rect.p0.y -= AA_PIXEL_RADIUS;
            local_coverage_rect.p1.y += AA_PIXEL_RADIUS;
#endif
            break;
        case PART_TOP:
            local_coverage_rect.p0.x = local_coverage_rect.p0.x + AA_PIXEL_RADIUS;
            local_coverage_rect.p1.x = local_coverage_rect.p1.x - AA_PIXEL_RADIUS;
            local_coverage_rect.p1.y = local_coverage_rect.p0.y + AA_PIXEL_RADIUS;
#ifdef SWGL_ANTIALIAS
            swgl_antiAlias(EDGE_AA_TOP);
#else
            local_coverage_rect.p0.y -= AA_PIXEL_RADIUS;
#endif
            break;
        case PART_RIGHT:
            local_coverage_rect.p0.x = local_coverage_rect.p1.x - AA_PIXEL_RADIUS;
#ifdef SWGL_ANTIALIAS
            swgl_antiAlias(EDGE_AA_RIGHT);
#else
            local_coverage_rect.p1.x += AA_PIXEL_RADIUS;
            local_coverage_rect.p0.y -= AA_PIXEL_RADIUS;
            local_coverage_rect.p1.y += AA_PIXEL_RADIUS;
#endif
            break;
        case PART_BOTTOM:
            local_coverage_rect.p0.x = local_coverage_rect.p0.x + AA_PIXEL_RADIUS;
            local_coverage_rect.p1.x = local_coverage_rect.p1.x - AA_PIXEL_RADIUS;
            local_coverage_rect.p0.y = local_coverage_rect.p1.y - AA_PIXEL_RADIUS;
#ifdef SWGL_ANTIALIAS
            swgl_antiAlias(EDGE_AA_BOTTOM);
#else
            local_coverage_rect.p1.y += AA_PIXEL_RADIUS;
#endif
            break;
        case PART_CENTER:
            local_coverage_rect.p0.x += edge_aa_offset(EDGE_AA_LEFT, qi.edge_flags);
            local_coverage_rect.p1.x -= edge_aa_offset(EDGE_AA_RIGHT, qi.edge_flags);
            local_coverage_rect.p0.y += edge_aa_offset(EDGE_AA_TOP, qi.edge_flags);
            local_coverage_rect.p1.y -= edge_aa_offset(EDGE_AA_BOTTOM, qi.edge_flags);
            break;
        case PART_ALL:
        default:
#ifdef SWGL_ANTIALIAS
            swgl_antiAlias(qi.edge_flags);
#else
            local_coverage_rect.p0.x -= edge_aa_offset(EDGE_AA_LEFT, qi.edge_flags);
            local_coverage_rect.p1.x += edge_aa_offset(EDGE_AA_RIGHT, qi.edge_flags);
            local_coverage_rect.p0.y -= edge_aa_offset(EDGE_AA_TOP, qi.edge_flags);
            local_coverage_rect.p1.y += edge_aa_offset(EDGE_AA_BOTTOM, qi.edge_flags);
#endif
            break;
    }

    vec2 local_pos = mix(local_coverage_rect.p0, local_coverage_rect.p1, aPosition);

    float device_pixel_scale = task.device_pixel_scale;
    if ((qi.quad_flags & QF_IGNORE_DEVICE_SCALE) != 0) {
        device_pixel_scale = 1.0f;
    }

    VertexInfo vi = write_vertex(
        local_pos,
        z,
        transform,
        task.content_origin,
        task.task_rect,
        device_pixel_scale,
        qi.quad_flags
    );

    v_color = prim.color;

    vec4 pattern_tx = prim.pattern_scale_offset;
    seg.rect = scale_offset_map_rect(pattern_tx, seg.rect);

    return PrimitiveInfo(
        scale_offset_map_point(pattern_tx, vi.local_pos),
        scale_offset_map_rect(pattern_tx, prim.bounds),
        scale_offset_map_rect(pattern_tx, prim.clip),
        seg,
        qi.edge_flags,
        qi.quad_flags,
        qh.pattern_input
    );
}

void antialiasing_vertex(PrimitiveInfo prim) {
#ifndef SWGL_ANTIALIAS
    // This does the setup that is required for init_tranform_vs.
    RectWithEndpoint xf_bounds = RectWithEndpoint(
        max(prim.local_prim_rect.p0, prim.local_clip_rect.p0),
        min(prim.local_prim_rect.p1, prim.local_clip_rect.p1)
    );
    vTransformBounds = vec4(xf_bounds.p0, xf_bounds.p1);

    vLocalPos = prim.local_pos;

    if (prim.edge_flags == 0) {
        v_flags_has_edge_mask = 0;
    } else {
        v_flags_has_edge_mask = 1;
    }
#endif
}

void main() {
    PrimitiveInfo prim = quad_primive_info();

    if ((prim.quad_flags & QF_IS_MASK) != 0) {
        v_flags_is_mask = 1;
    } else {
        v_flags_is_mask = 0;
    }

    antialiasing_vertex(prim);
    pattern_vertex(prim);
}
#endif

#ifdef WR_FRAGMENT_SHADER
vec4 pattern_fragment(vec4 base_color);

float antialiasing_fragment() {
    float alpha = 1.0;
#ifndef SWGL_ANTIALIAS
    if (v_flags_has_edge_mask != 0) {
        alpha = rectangle_aa_fragment(vLocalPos);
    }
#endif
    return alpha;
}

void main() {
    vec4 base_color = v_color;
    base_color *= antialiasing_fragment();
    vec4 output_color = pattern_fragment(base_color);

    if (v_flags_is_mask != 0) {
        output_color = output_color.rrrr;
    }

    oFragColor = output_color;
}

#endif
